package com.unitd.frame.mybatis.plugin.pagination;

import com.unitd.frame.mybatis.interceptor.IInterceptorHandler;
import com.unitd.frame.mybatis.crud.builder.SqlTemplate;
import com.unitd.frame.mybatis.exception.MybatisHanlerInitException;
import com.unitd.frame.mybatis.parser.EntityInfo;
import com.unitd.frame.mybatis.parser.MybatisMapperParser;
import com.unitd.frame.mybatis.plugin.MybatisPluginInterceptor;
import com.unitd.frame.mybatis.plugin.PluginConstant;
import com.unitd.frame.mybatis.plugin.pagination.annotation.Pageable;
import com.unitd.frame.mybatis.plugin.pagination.comm.DbTypeEnum;
import com.unitd.frame.mybatis.plugin.pagination.comm.Page;
import com.unitd.frame.mybatis.plugin.pagination.comm.PageParams;
import com.unitd.frame.mybatis.plugin.pagination.executor.PageExecutor;
import com.unitd.frame.mybatis.plugin.pagination.helper.PageSqlUtils;
import com.unitd.frame.comm.utils.StringUtils;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;
import java.sql.SQLException;
import java.util.*;

/**
 * @desc Mybatis的自动分页拦截处理, 实现Mybatis的自动分页和插件的拦截处理器接口
 * @filename PaginationHandler.java
 * @copyright www.unitd.com
 * @date 2017/2/5
 * @author Hudan
 * @version 1.0
 */
public class PaginationHandler implements IInterceptorHandler {

	private static Logger logger = LoggerFactory.getLogger(PaginationHandler.class);

	/** 每页记录数 */
	private static final String PARAMETER_SIZE = "pageSize";
	/** 偏移量,第一条数据再表中的位置 */
	private static final String PARAMETER_OFFSET = "offset";
	/** 限定数,每页的数量 */
	private static final String PAGE_LIMIT_SUFFIX = "_PageLimit";
	/** 总页数 */
	private static final String PAGE_COUNT_SUFFIX = "_PageCount";

	private Map<String, Boolean> pageMappedStatements = new HashMap<>();
	private List<String> createdPageMappedStatements = new ArrayList<>();
	private DbTypeEnum dbType = DbTypeEnum.MYSQL;

	/**
	 * @param context mybatis的上下文对象
	 * @desc 进入拦截器, 开始进行拦截处理
	 */
	@Override
	public void start(MybatisPluginInterceptor context) {
		// 设置应用使用的数据库类型
		setDbType(context.getProperty(PluginConstant.DB_TYPE));
		logger.info("dbType:{}", dbType.name());

		// 获取mapper.xml的对象信息,并解析其中的与分页相关的(有直接返回Page对象和Pageable注解两种)
		List<EntityInfo> entityInfos = MybatisMapperParser.getEntityInfos();
		for (EntityInfo ei : entityInfos) {
			Class<?> mapperClass = ei.getMapperClass();
			Method[] methods = mapperClass.getDeclaredMethods();
			for (Method method : methods) {
				if (method.getReturnType() == Page.class) {
					String msId = ei.getMapperClass().getName() + "." + method.getName();

					boolean withPageParams = false;
					Class<?>[] parameterTypes = method.getParameterTypes();
					for (Class<?> clazz : parameterTypes) {
						if (withPageParams = (clazz == PageParams.class || clazz.getSuperclass() == PageParams.class)) {
							break;
						}
					}

					if (!withPageParams) {
						throw new MybatisHanlerInitException(String.format("method[%s] returnType is:Page,but not found Parameter[PageParams] in Parameters list", method.getName()));
					}
					pageMappedStatements.put(msId, true);
				} else if (method.isAnnotationPresent(Pageable.class)) {
					String msId = ei.getMapperClass().getName() + "." + method.getName();
					pageMappedStatements.put(msId, false);
				}
			}
		}
	}

	/**
	 * @desc 设置不同拦截器的加载顺序
	 * @return int
	 */
	@Override
	public int interceptorOrder() {
		return 3;
	}

	/**
	 * @desc 拦截器开启调用拦截处理
	 * @param invocation 调用器
	 */
	@Override
	public Object onInterceptor(Invocation invocation) throws Throwable {

		final Executor executor = (Executor) invocation.getTarget();
		final Object[] args = invocation.getArgs();
		final MappedStatement orignMappedStatement = (MappedStatement) args[0];

		if (!orignMappedStatement.getSqlCommandType().equals(SqlCommandType.SELECT)) return null;

		PageParams pageParams = PageExecutor.getPageParams();
		if (pageParams == null && !pageMappedStatements.keySet().contains(orignMappedStatement.getId())) return null;

		final RowBounds rowBounds = (RowBounds) args[2];
		final ResultHandler resultHandler = (ResultHandler) args[3];
		final Object parameter = args[1];

		BoundSql boundSql;
		if (args.length == 4) {
			boundSql = orignMappedStatement.getBoundSql(parameter);
		} else {
			boundSql = (BoundSql) args[5];
		}

		if (pageParams == null && pageMappedStatements.get(orignMappedStatement.getId())) {
			if (parameter instanceof Map) {
				Collection parameterValues = ((Map) parameter).values();
				for (Object val : parameterValues) {
					if (val instanceof PageParams) {
						pageParams = (PageParams) val;
						break;
					}
				}
			} else {
				pageParams = (PageParams) parameter;
			}
		}

		if (pageParams == null) return null;

		// 查询总数
		MappedStatement countMappedStatement = getCountMappedStatement(orignMappedStatement);
		Long total = executeQueryCount(executor, countMappedStatement, parameter, boundSql, rowBounds, resultHandler);

		// 按分页查询
		MappedStatement limitMappedStatement = getLimitMappedStatementIfNotCreate(orignMappedStatement);

		List<?> datas;
		BoundSql pageBoundSql;
		if (limitMappedStatement == null) {
			String pageSql = PageSqlUtils.getLimitSQL(dbType, boundSql.getSql(), pageParams);
			pageBoundSql = new BoundSql(orignMappedStatement.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter);

			datas = executor.query(orignMappedStatement, parameter, RowBounds.DEFAULT, resultHandler, null, pageBoundSql);
		} else {
			pageBoundSql = limitMappedStatement.getBoundSql(parameter);
			pageBoundSql.setAdditionalParameter(PARAMETER_OFFSET, pageParams.getOffset());
			pageBoundSql.setAdditionalParameter(PARAMETER_SIZE, pageParams.getPageSize());

			datas = executor.query(limitMappedStatement, parameter, RowBounds.DEFAULT, resultHandler, null, pageBoundSql);
		}

		Page<Object> page = new Page<>(pageParams, total, (List<Object>) datas);

		List<Page<?>> list = new ArrayList<>(1);
		list.add(page);
		return list;
	}

	/**
	 * @desc 结束拦截调用处理
	 * @param invocation 调用器
	 * @param result     结果对象
	 */
	@Override
	public void onFinished(Invocation invocation, Object result) {
		PageExecutor.clearPageParams();
	}

	/**
	 * @desc 关闭拦截器, 退出拦截
	 */
	@Override
	public void close() {
	}

	private void setDbType(String dbType) {
		if (StringUtils.isBlank(dbType)) return;
		DbTypeEnum[] dbTypes = DbTypeEnum.values();
		for (DbTypeEnum dt : dbTypes) {
			if (dt.name().equalsIgnoreCase(dbType)) {
				this.dbType = dt;
				break;
			}
		}
	}

	/**
	 * @desc 新建count查询的MappedStatement
	 * @param ms Mybatis的mapper执行器
	 * @return MappedStatement
	 */
	private MappedStatement getCountMappedStatement(MappedStatement ms) {

		String newMsId = ms.getId() + PAGE_COUNT_SUFFIX;

		MappedStatement statement;
		Configuration configuration = ms.getConfiguration();

		try {
			statement = configuration.getMappedStatement(newMsId);
			if (statement != null) return statement;
		} catch (Exception e) {
		}

		synchronized (configuration) {

			if (configuration.hasStatement(newMsId)) return configuration.getMappedStatement(newMsId);

			MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), newMsId, ms.getSqlSource(), ms.getSqlCommandType());
			builder.resource(ms.getResource());
			builder.fetchSize(ms.getFetchSize());
			builder.statementType(ms.getStatementType());
			builder.keyGenerator(ms.getKeyGenerator());
			if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
				StringBuilder keyProperties = new StringBuilder();
				for (String keyProperty : ms.getKeyProperties()) {
					keyProperties.append(keyProperty).append(",");
				}
				keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
				builder.keyProperty(keyProperties.toString());
			}
			builder.timeout(ms.getTimeout());
			builder.parameterMap(ms.getParameterMap());
			// count查询返回值int
			List<ResultMap> resultMaps = new ArrayList<>();
			String id = newMsId + "-Inline";
			ResultMap resultMap = new ResultMap.Builder(configuration, id, Long.class, new ArrayList<>(0)).build();
			resultMaps.add(resultMap);
			builder.resultMaps(resultMaps);

			builder.resultSetType(ms.getResultSetType());
			builder.cache(ms.getCache());
			builder.flushCacheRequired(ms.isFlushCacheRequired());
			builder.useCache(ms.isUseCache());

			statement = builder.build();
			configuration.addMappedStatement(statement);
			return statement;
		}

	}

	/**
	 * @desc 执行count查询
	 * @param executor 查询器
	 * @param countMs 查询的MappedStatement
	 * @param parameter 查询参数
	 * @param boundSql 其他附属SQL
	 * @param rowBounds 行参数
	 * @param resultHandler 结果处理器
	 * @return Long
	 */
	@SuppressWarnings("rawtypes")
	private Long executeQueryCount(Executor executor, MappedStatement countMs,
								   Object parameter, BoundSql boundSql,
								   RowBounds rowBounds, ResultHandler resultHandler) throws IllegalAccessException, SQLException {
		CacheKey countKey = executor.createCacheKey(countMs, parameter, RowBounds.DEFAULT, boundSql);

		String orignSql = boundSql.getSql().replaceAll(";$", "");
		// count sql
		String countSql = PageSqlUtils.getCountSql(orignSql);

		BoundSql countBoundSql = new BoundSql(countMs.getConfiguration(), countSql, boundSql.getParameterMappings(),
				parameter);
		// 执行 count 查询
		Object countResultList = executor.query(countMs, parameter, RowBounds.DEFAULT, resultHandler, countKey,
				countBoundSql);
		return (Long) ((List) countResultList).get(0);
	}

	/**
	 * @desc 按分页查询
	 * @param orignMappedStmt 查询的MappedStatement
	 * @return MappedStatement
	 */
	private MappedStatement getLimitMappedStatementIfNotCreate(MappedStatement orignMappedStmt) {

		String msId = orignMappedStmt.getId() + PAGE_LIMIT_SUFFIX;

		MappedStatement statement;
		Configuration configuration = orignMappedStmt.getConfiguration();

		try {
			statement = configuration.getMappedStatement(msId);
			if (statement != null) return statement;
		} catch (Exception e) {
		}

		try {
			//动态生成过，但是报错了
			if (createdPageMappedStatements.contains(orignMappedStmt.getId())) return null;

			EntityInfo entityInfo = MybatisMapperParser.getEntityInfoByMapper(orignMappedStmt.getId().substring(0, orignMappedStmt.getId().lastIndexOf(".")));

			String orignSql = entityInfo.getMapperSqls().get(orignMappedStmt.getId());
			synchronized (configuration) {
				createdPageMappedStatements.add(orignMappedStmt.getId());

				if (configuration.hasStatement(msId)) return configuration.getMappedStatement(msId);
				String sql = PageSqlUtils.getLimitSQL(dbType, orignSql);

				sql = String.format(SqlTemplate.SCRIPT_TEMAPLATE, sql);
				SqlSource sqlSource = configuration.getDefaultScriptingLanguageInstance().createSqlSource(configuration, sql, Object.class);

				MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, msId, sqlSource, SqlCommandType.SELECT);

				statementBuilder.resource(orignMappedStmt.getResource());
				statementBuilder.statementType(orignMappedStmt.getStatementType());
				statementBuilder.parameterMap(orignMappedStmt.getParameterMap());
				statementBuilder.resultSetType(orignMappedStmt.getResultSetType());
				statementBuilder.resultMaps(orignMappedStmt.getResultMaps());
				statement = statementBuilder.build();
				configuration.addMappedStatement(statement);

				return statement;
			}
		} catch (Exception e) {
			return null;
		}
	}
}